package com.xuzhiguang.light.gateway.core.filter;

import com.xuzhiguang.light.gateway.common.response.GatewayResponse;
import com.xuzhiguang.light.gateway.core.context.GatewayContext;
import com.xuzhiguang.light.gateway.core.context.GatewayContextConstant;
import lombok.Getter;

import java.util.function.Consumer;

/**
 * @author xuzhiguang
 */
public abstract class AbstractRoutingGatewayFilter<R, S> implements GatewayFilter {

    private static final int ORDER = 0;


    @Override
    public boolean shouldRun(GatewayContext context) {
        String scheme = context.getRoute().getUri().getScheme();
        return GatewayFilter.super.shouldRun(context) && match(scheme);
    }

    @Override
    public void doFilter(GatewayContext context, GatewayFilterChain chain) throws Throwable {

        R request = toRoutingRequest(context);
        doRequest(context, request, s -> complete(context, toGatewayResponse(s.getResponse()), s.getThrowable(), chain));
    }

    @Override
    public int getOrder() {
        return ORDER;
    }

    /**
     * 协议是否匹配
     * @param scheme
     * @return
     */
    protected abstract boolean match(String scheme);

    /**
     * 转换成对应的 Request
     * @param context
     * @return
     */
    protected abstract R toRoutingRequest(GatewayContext context);

    /**
     * 转换成 gatewayResponse
     * @param response
     * @return
     */
    protected abstract GatewayResponse toGatewayResponse(S response);


    /**
     * 发送请求
     * @param context
     * @param request
     * @param consumer
     */
    protected abstract void doRequest(GatewayContext context, R request, Consumer<ResponseWrapper<S>> consumer);

    /**
     * 完成
     * @param context
     * @param gatewayResponse
     * @param throwable
     */
    protected void complete(GatewayContext context, GatewayResponse gatewayResponse, Throwable throwable, GatewayFilterChain chain) {

        // 记录 route 响应时间
        context.putAttribute(GatewayContextConstant.ROUTE_RECEIVED_TIME_ATTR, System.currentTimeMillis());

        if (throwable != null) {
            context.setThrowable(throwable);
        }

        context.setResponse(gatewayResponse);

        chain.doFilter(context);
    }

    @Getter
    public static class ResponseWrapper<S> {

        private final S response;

        private final Throwable throwable;


        public ResponseWrapper(S response, Throwable throwable) {
            this.response = response;
            this.throwable = throwable;
        }
    }


}
